# encoding=utf-8
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np

#选择题识别

mheight = 1800
mwidth = 1720

# 四点变换函数
def order_points(pts):
    # initialize a list of coordinates that will be ordered
    # such that the first entry in the list is the top-left,
    # the second entry is the top-right, the third is the
    # bottom-right, and the fourth is the bottom-left
    rect = np.zeros((4, 2), dtype="float32")

    # the top-left point will have the smallest sum, whereas
    # the bottom-right point will have the largest sum
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    # now, compute the difference between the points, the
    # top-right point will have the smallest difference,
    # whereas the bottom-left will have the largest difference
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    # return the ordered coordinates
    return rect

def four_point_transform(image, pts):
    # obtain a consistent order of the points and unpack them
    # individually
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    # compute the width of the new image, which will be the
    # maximum distance between bottom-right and bottom-left
    # x-coordiates or the top-right and top-left x-coordinates
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))

    # compute the height of the new image, which will be the
    # maximum distance between the top-right and bottom-right
    # y-coordinates or the top-left and bottom-left y-coordinates
    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))

    # now that we have the dimensions of the new image, construct
    # the set of destination points to obtain a "birds eye view",
    # (i.e. top-down view) of the image, again specifying points
    # in the top-left, top-right, bottom-right, and bottom-left
    # order
    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")

    # compute the perspective transform matrix and then apply it
    M = cv.getPerspectiveTransform(rect, dst)
    warped = cv.warpPerspective(image, M, (maxWidth, maxHeight))

    # return the warped image
    return warped

# 1. 预处理
# 读入原始图片i i2 i11
orginImg = cv.imread("img/i2.jpg")
#灰度图像
grayImg = cv.cvtColor(orginImg, cv.COLOR_BGR2GRAY)
# blurred = cv.GaussianBlur(grayImg,(3,3),0)
# 将灰度图像转化为二值图(自适应阈值) http://blog.csdn.net/on2way/article/details/46812121
binaryImg = cv.adaptiveThreshold(grayImg, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
'''6个参数：
第一个原始图像
第二个像素值上限
第三个自适应方法Adaptive Method: 
— cv2.ADAPTIVE_THRESH_MEAN_C ：领域内均值 
—cv2.ADAPTIVE_THRESH_GAUSSIAN_C ：领域内像素点加权和，权 重为一个高斯窗口
第四个值的赋值方法：只有cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV
第五个Block size:规定领域大小（一个正方形的领域）
第六个常数C，阈值等于均值或者加权值减去这个常数（为0相当于阈值 就是求得领域内均值或者加权值） 
这种方法理论上得到的效果更好，相当于在动态自适应的调整属于自己像素点的阈值，而不是整幅图像都用一个阈值
'''
# canny边缘检测 以及 轮廓检测 https://segmentfault.com/a/1190000003742461
# http://blog.csdn.net/sunny2038/article/details/9202641
cannyImg = cv.Canny(binaryImg, 50, 150)
# 寻找出所有轮廓集合 http://blog.sina.com.cn/s/blog_662c78590100z0rg.html
contours = cv.findContours(cannyImg, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
'''参数： 
  image -- 要查找轮廓的原图像
  mode -- 轮廓的检索模式，它有四种模式：
                 cv2.RETR_EXTERNAL  表示只检测外轮廓                                  
                 cv2.RETR_LIST 检测的轮廓不建立等级关系
                 cv2.RETR_CCOMP 建立两个等级的轮廓，上面的一层为外边界，里面的一层为内孔的边界信息。如果内孔内还有一个连通物体，
                                                    这个物体的边界也在顶层。
                cv2.RETR_TREE 建立一个等级树结构的轮廓。
  method --  轮廓的近似办法：
                      cv2.CHAIN_APPROX_NONE 存储所有的轮廓点，相邻的两个点的像素位置差不超过1，即max （abs (x1 - x2), abs(y2 - y1) == 1
                      cv2.CHAIN_APPROX_SIMPLE压缩水平方向，垂直方向，对角线方向的元素，只保留该方向的终点坐标，例如一个矩形轮廓只需
                                                                            4个点来保存轮廓信息
                    cv2.CHAIN_APPROX_TC89_L1，CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
返回值：
   cv2.findContours()函数返回两个值：
    contours -- 轮廓本身，它是一个list,list 中每个元素都是图像中的一个轮廓，用Numpy中的ndarray表示 ，每个轮廓是一个ndarray,每个ndarray
                          是轮廓上的点的集合。轮廓中并不存储轮廓上所有的点，而只存储可以用直线描述轮廓的点的个数，比如一个“正立”的矩形只有4个
                          点元素。
    还有一个是每条轮廓对应的属性
'''
contours = contours[1]
# 按面积降序
contours = sorted(contours, key=cv.contourArea, reverse=True)
# 曲线逼近
# 简化边界-方便找出轮廓
approx = cv.approxPolyDP(contours[0], 150, True)
# cv.drawContours(orginImg, approx, -1, (0, 0, 255), 30)
#为了显示，用一张原图做变换
orginWImg = four_point_transform(orginImg, approx.reshape(4, 2))
#变换后图像
warpedImg = four_point_transform(grayImg, approx.reshape(4, 2))

#自适应阈值
binaryWImg = cv.adaptiveThreshold(warpedImg, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 101, 2)
#重新调整大小，方便后期裁剪
binaryWImg = cv.resize(binaryWImg, (mwidth, mheight), cv.INTER_LANCZOS4)
#为了显示，用原图来进行相同的操作
orginWImg = cv.resize(orginWImg, (mwidth, mheight), cv.INTER_LANCZOS4)
#存储图像，便于数字识别
# cv.imwrite(".\img\digetOcrTest.jpg",orginWImg)
'''interpolation - 插值方法。共有5种：
１）INTER_NEAREST - 最近邻插值法
２）INTER_LINEAR - 双线性插值法（默认）
３）INTER_AREA - 基于局部像素的重采样（resampling using pixel area relation）。对于图像抽取（image decimation）来说，这可能是一个更好的方法。但如果是放大图像时，它和最近邻法的效果类似。
４）INTER_CUBIC - 基于4x4像素邻域的3次插值法
５）INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos插值
收缩图像，那么使用重采样差值法效果最好；如果想要放大图像，那么最好使用3次差值法或者线性差值法
'''
#进行均值滤波
meanImg = cv.blur(binaryWImg, (25, 25))
#进行固定阈值过滤
meanImg = cv.threshold(meanImg, 70, 255, cv.THRESH_BINARY)[1]
#前面已经固定了图片大小，这里可以进行裁剪
meanImg = meanImg[580:1350, :]
orginWImg = orginWImg[580:1350, :]
#对图片进行腐蚀膨胀3次操作，让选择题突出，便于处理
meanImg = cv.erode(meanImg, None, iterations=3)
#x寻找轮廓，以树结构存储，便于确立中心点
contours = cv.findContours(meanImg, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)[1]

#确定答案选择
def Answer(i):
	if i == 1:
		return 'A'
	elif i == 2:
		return 'B'
	elif i == 3:
		return 'C'
	elif i == 4:
		return 'D'
	else:
		return 'Wrong'

#是否第一个，第一个是最大的轮廓，需要过滤掉
isFirst = True
#根据试卷的宽度确定每个选择题的宽度
widthAnswer = meanImg.shape[1] / 20
#每5个选择题的宽度
wNUm = widthAnswer * 5
#答案的坐标
answerP = []

#遍历所有选择题坐标
for contour in contours:
	#跳过第一个最大的（全部）
	if isFirst:
		isFirst = False
		continue
	#得到最小矩阵
	cRect = cv.minAreaRect(contour)
	#得到当前选择题的中心点坐标
	cX = int(cRect[0][0])
	cY = int(cRect[0][1])
	#存入答案的中心点坐标
	answerP.append((cX,cY))
	# print(int(cX / widthAnswer))
	# colA = (int(cX / widthAnswer) + 1) % 5 -1
	# print(str(colA)  + "   答案" + Answer(colA))
	#在原图上画圆圈，标识出答案
	cv.circle(orginWImg, (cX,cY), 7, (0, 0, 255), -1)

#对答案坐标 进行 高度 排序
answerP = sorted(answerP, key=lambda a:a[1])
#记录已经存入答案数量
countNum = 1
#每一行第一个题号
aNum = 1
#根据试卷每列首题编号
rowNum = [1,2,3,4,5,21,22,23,24,25,41,42,43,44,45,0]
# 存储的选择题答案
answer = []

#遍历每个答案坐标
for i in answerP:
	# print(i,end='  ')
	#计算第几列，确定选项 0ABCD
	colA = int(i[0] / widthAnswer)%5
	# print(Answer(colA),end=" ")
	#确定题号
	numT = aNum + int(i[0] / wNUm)*5
	# print(numT)
	answer.append((numT,Answer(colA)))
	if countNum <= 40:
		if countNum % 4 == 0:
			aNum = rowNum[int(countNum/4)]
	else:
		if countNum % 2 == 0:
			aNum = rowNum[int((countNum-40) / 2) + 10]
	countNum += 1

#对答案序号进行排序，便于展示
answer = sorted(answer,key=lambda x:x[0])
#输出所有的答案
print(answer)

# 高度范围  560 - 1350   方块比例 宽大于50 高大于25
# contours = cv.findContours(meanImg,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
#cv.drawContours(binaryWImg,contours[2],-1,(0,0,255),3,lineType=0)

# 绘图查看坐标长宽
# plt.figure()
# plt.subplot(131)
# plt.imshow(binaryWImg,cmap='gray')
# plt.show()

# cv.namedWindow("OrginImage",0)
# cv.imshow("OrginImage",orginImg)
# cv.namedWindow("GrayImage",0)
# cv.imshow("GrayImage",grayImg)
# cv.namedWindow("bluImage",0)
# cv.imshow("bluImage",binaryImg)
# cv.namedWindow("CannyImage",0)
# cv.imshow("CannyImage",cannyImg)
# cv.namedWindow("WarpedImage",0)
# cv.imshow("WarpedImage",warpedImg)
# cv.namedWindow("BinaryWImage",0)
# cv.imshow("BinaryWImage",binaryWImg)
# cv.namedWindow("MeanWImage", 0)
# cv.imshow("MeanWImage", meanImg)
cv.namedWindow("OrginWImage", 0)
cv.imshow("OrginWImage", orginWImg)

cv.waitKey(0)
cv.destoryAllWindows()
# cv.destoryWindows("OrginImage",0)
# cv.destoryWindows("GrayImage")
# cv.destoryWindows("bluImage")
# cv.destoryWindows("CannyImage")

# cv.destoryWindows("WarpedImage",0)
# cv.destoryWindows("BinaryWImage",0)


